﻿#include "render_system/buffer_object.h"

#include "render_system/gl_header.h"
#include "utility.h"

/*
enum Type {
ARRAY = 0,
ATOMIC_COUNTER,
COPY_READ, 
COPY_WRITE, 
DRAW_INDIRECT,
ELEMENT_ARRAY,
PIXEL_PACK,
PIXEL_UNPACK,
TEXTURE,
TRANSFORM_FEEDBACK,
UNIFORM
};
*/
const GLenum BufferTargetTable[] = {
    GL_ARRAY_BUFFER,
    GL_ATOMIC_COUNTER_BUFFER,
    GL_COPY_READ_BUFFER, 
    GL_COPY_WRITE_BUFFER, 
    GL_DRAW_INDIRECT_BUFFER,
    GL_ELEMENT_ARRAY_BUFFER,
    GL_PIXEL_PACK_BUFFER,
    GL_PIXEL_UNPACK_BUFFER,
    GL_TEXTURE_BUFFER,
    GL_TRANSFORM_FEEDBACK_BUFFER,
    GL_UNIFORM_BUFFER
};


/*
enum Usage {
STREAM_DRAW = 0,
STREAM_READ,
STREAM_COPY,
STATIC_DRAW,
STATIC_READ,
STATIC_COPY,
DYNAMIC_DRAW,
DYNAMIC_READ,
DYNAMIC_COPY
};
*/
const GLenum BufferUsageTable[] = {
    GL_STREAM_DRAW,
    GL_STREAM_READ,
    GL_STREAM_COPY,
    GL_STATIC_DRAW,
    GL_STATIC_READ,
    GL_STATIC_COPY,
    GL_DYNAMIC_DRAW,
    GL_DYNAMIC_READ,
    GL_DYNAMIC_COPY
};


BufferObject::BufferObject()
    : _uID(0)
    , _type(ARRAY)
    , _usage(STREAM_DRAW)
    , _pixelFormat()
    , _uSizeInBytes(0)
    , _pData()
{
}


BufferObject::~BufferObject()
{
    if (0 != _uID)
    {
        glDeleteBuffers(1, &_uID);
        _uID = 0;
    }
}


void BufferObject::bind()
{
    if (0 != _uID)
    {
        glBindBuffer(BufferTargetTable[_type], _uID);
    }
}


void BufferObject::unBind()
{
    if (0 != _uID)
    {
        glBindBuffer(BufferTargetTable[_type], 0);
    }
}


void BufferObject::read(
    const size_t uSizeInBytes,
    const PixelFormat& pixelformat,
    const Type type,
    const Usage usage,
    const void* pData)
{
    try
    {
        if (1 > uSizeInBytes)
        {
            throw std::exception("input size too small.");
        }

        _pData = shared_array(new char[uSizeInBytes]);
        memcpy(_pData.get(), pData, uSizeInBytes);

        _type = type;
        _uSizeInBytes = uSizeInBytes;
        _pixelFormat = pixelformat;
        _usage = usage;

        _copy2gpu();
    }
    catch(const std::bad_alloc&)
    {
        std::string err("fail to malloc buffer for internal storage.");
        Log::error(err.c_str());
        throw std::exception(err.c_str());
    }
    catch(const std::exception& e)
    {
        Log::error(e.what());
        throw;
    }
}


size_t BufferObject::ID() const
{
    return _uID;
}


PixelFormat BufferObject::pixelFormat() const
{
    return _pixelFormat;
}


size_t BufferObject::glFormat() const
{
    return _pixelFormat.glFormat();
}


size_t BufferObject::glConponentType() const
{
    return _pixelFormat.glStorageType();
}


size_t BufferObject::channels() const
{
    return _pixelFormat.channels();
}


size_t BufferObject::numberOfData() const
{
    return _uSizeInBytes / _pixelFormat.bytes();
}


void BufferObject::_copy2gpu()
{
    try
    {
        GLuint id;
        glGenBuffers(1, &id);
        if (0 == id)
        {
            throw std::exception("fail to generate buffer object id.");
        }

        _uID = id;

        glBindBuffer(BufferTargetTable[_type], _uID);

        glBufferData(BufferTargetTable[_type], _uSizeInBytes, _pData.get(), 
            BufferUsageTable[_usage]);

        glBindBuffer(BufferTargetTable[_type], 0); // unbind vbo
    }
    catch(const std::exception& e)
    {
        if (0 != _uID)
        {
            glDeleteBuffers(1, &_uID);
            _uID = 0;
        }
        Log::error(e.what());
        throw;
    }
}

